package com.zws.cucumber.glue;

import com.zws.cucumber.plugin.procedure.ZwsProcedureUtil;
import com.zws.cucumber.util.colorfullog.ZwsColorfulLogger;
import com.zws.cucumber.util.colorfullog.ZwsPrintTableHelper;
import com.zws.cucumber.var.ZwsVar;
import io.cucumber.datatable.DataTable;
import io.cucumber.java.en.Given;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.slf4j.Logger;

import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import static com.zws.cucumber.expander.ZwsStr.expand;
import static com.zws.cucumber.expr.ZwsExpr.evaluate;

/**
 * author: zws
 */
public class ZwsProcedureStepsDef {

    private static final Logger Logger = ZwsColorfulLogger.of(ZwsProcedureStepsDef.class);

    @Given("invoke procedure {string}")
    public void invokeProcedure(String procedureName) throws Throwable {
        invokeProcedureWithArgumentMap(procedureName, new HashMap<>(), null);
    }

    @Given("invoke {string}")
    public void invoke(String procedureName) throws Throwable {
        invokeAndSaveTo(procedureName, null);
    }

    @Given("invoke {string}, save to {string}")
    public void invokeAndSaveTo(String procedureName, String savedVarName) throws Throwable {
        invokeProcedureWithArgumentMap(procedureName, new HashMap<>(), savedVarName);
    }

    @Given("invoke procedure {string} with ordered arguments")
    public void invokeProcedureWithOrderedArguments(String procedureName, DataTable argumentTable) throws Throwable {
        invokeProcedureWithOrderedArgumentsAndSaveTo(procedureName, null, argumentTable);
    }

    @Given("invoke {string} with ordered arguments")
    public void invokeWithOrderedArguments(String procedureName, DataTable argumentTable) throws Throwable {
        invokeProcedureWithOrderedArgumentsAndSaveTo(procedureName, null, argumentTable);
    }

    @Given("invoke {string} with ordered arguments, save to {string}")
    public void invokeProcedureWithOrderedArgumentsAndSaveTo(String procedureName, String savedVarName, DataTable argumentTable) throws Throwable {
        List<String> argumentList = argumentTable.asList()
                .stream()
                .map(e -> (String) evaluate(e, expand(e)))
                .collect(Collectors.toList());
        Map<String, String> argumentMap = ZwsProcedureUtil.assembleArgumentMap(procedureName, argumentList);
        invokeProcedureWithArgumentMap(procedureName, argumentMap, savedVarName);
    }

    @Given("invoke procedure {string} with named arguments")
    public void invokeProcedureWithNamedArguments(String procedureName, DataTable argumentTable) throws Throwable {
        invokeProcedureWithNamedArgumentsAndSaveTo(procedureName, null, argumentTable);
    }

    @Given("invoke {string} with arguments")
    public void invokeWithNamedArguments(String procedureName, DataTable argumentTable) throws Throwable {
        invokeProcedureWithNamedArgumentsAndSaveTo(procedureName, null, argumentTable);
    }

    @Given("invoke {string} with arguments, save to {string}")
    public void invokeProcedureWithNamedArgumentsAndSaveTo(String procedureName, String savedVarName, DataTable argumentTable) throws Throwable {
        // convert to argument map
        Map<String, String> argumentMap = new HashMap<>();
        argumentTable.<String, String>asMap(String.class, String.class)
                .forEach((key, value) -> argumentMap.put(key, evaluate(key, expand(value))));
        // invoke
        invokeProcedureWithArgumentMap(procedureName, argumentMap, savedVarName);
    }

    @Given("return variable {string}")
    public void returnVariable(String variableName) {
        Object result = ZwsVar.get(variableName);
        ZwsProcedureUtil.setResult(result);
    }

    @Given("return value {string}")
    public void returnValue(String value) {
        ZwsProcedureUtil.setResult(expand(value));
    }

    private void invokeProcedureWithArgumentMap(String procedureName, Map<String, String> argumentMap, String savedVarName) throws Throwable {
        // check argument name
        ZwsProcedureUtil.checkArgumentNames(procedureName, argumentMap.keySet());
        // invoke
        ZwsProcedureUtil.runProcedureWithArguments(procedureName, argumentMap);
        if (savedVarName != null) {
            ZwsVar.put(savedVarName, ZwsProcedureUtil.getLastResult());
        }
    }

    @Given("print procedure")
    public void printProcedure() {

        List<String> keyList = Arrays.asList("Name", "Arguments");
        List<Map<String, Object>> rowList = ZwsProcedureUtil.procedureMap.keySet()
                .stream()
                .map(k -> {
                    Map<String, Object> map = new HashMap<>();
                    map.put("Name", k);
                    List<String> args = ZwsProcedureUtil.argumentsMap.get(k);
                    map.put("Arguments", CollectionUtils.isEmpty(args) ? "" : String.join("\n", args));
                    return map;
                })
                .collect(Collectors.toList());

        ZwsPrintTableHelper.printTable0("Procedures", Pair.of(keyList, rowList));
    }

    // fixme: remove it later, just for debug
    @Given("throw exception")
    public void throwException() {
        throw new RuntimeException();
    }

}
